/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package geography;
import java.util.*;
import java.io.*;
import data.LocalData;
/**
 *
 * @author Isaac Noble
 */
public class Geography {

    private static boolean built = false;

    public static double MaxLatitude = -Double.MAX_VALUE;
    public static double MinLatitude = Double.MAX_VALUE;
    public static double MaxLongitude = -Double.MAX_VALUE;
    public static double MinLongitude = Double.MAX_VALUE;

    public static double LatitudeNoise = 0.0;
    public static double LongitudeNoise = 0.0;

    public enum GeoType
    {
        CITY,
        COUNTY,
        DMA,
        STATE,
        COUNTRY
    }

    private static HashMap<String, City> cities = new HashMap<String,City>();
    private static HashMap<String, County> counties;
    private static HashMap<String, DMA> DMAs;
    private static HashMap<String, State> states = new HashMap<String,State>();

    public static void Build() throws IOException
    {
        if(!built)
        {
           InputStream in_stream = LocalData.GetInputStream(LocalData.Input.GEO_FILE);
            InputStreamReader is_reader = new InputStreamReader(in_stream);
            BufferedReader reader = new BufferedReader(is_reader);

            String line = reader.readLine();
            while ((line = reader.readLine()) != null) {
                City info = new City(new Values(line.split(",")));
                if (info.latitude < MinLatitude)
                {
                    MinLatitude = info.latitude;
                }
                if (info.latitude > MaxLatitude)
                {
                    MaxLatitude = info.latitude;
                }
                if (info.longitude < MinLongitude)
                {
                    MinLongitude = info.longitude;
                }
                if (info.longitude > MaxLongitude)
                {
                    MaxLongitude = info.longitude;
                }
            }
            reader.close();
            is_reader.close();
            in_stream.close();

            MinLatitude -= 0.1 * 0.5;
            MaxLatitude += 0.1 * 0.5;
            MinLongitude -= 0.1 * 0.5;
            MaxLongitude += 0.1 * 0.5;
            
            built = true;
        }
    }

    public static Collection<City> GetCities()
    {
        return cities.values();
    }

    public static void AddAlias(GeoType type, String OldName, String NewName)
    {
        switch (type)
        {
            case CITY:
                cities.put(NewName, cities.get(OldName));
            case STATE:
                states.put(NewName, states.get(OldName));
            default:
                return;
        }
    }

    public static GeoInfo GetInfo(GeoType type, String name)
    {
        switch (type)
        {
            case CITY:
                return GetCity(name);
            case STATE:
                return GetState(name);
            default:
                return null;
        }
    }

    public static City GetCity(String name)
    {
        if(cities.containsKey(name))
        {
            return cities.get(name);
        }
        else
        {
            return null;
        }
    }


    public static State GetState(String name)
    {
        if(states.containsKey(name))
        {
            return states.get(name);
        }
        else
        {
            Iterator<State> iter = states.values().iterator();
            while(iter.hasNext())
            {
                State state = iter.next();
                if(state.state_code.equalsIgnoreCase(name))
                {
                    states.put(name, state);
                    return state;
                }
            }
            return null;
        }
    }

    public static boolean Exists(GeoType type, String name)
    {
        switch (type)
        {
            case CITY:
                return cities.containsKey(name);
            case STATE:
                return states.containsKey(name);
            default:
                return false;
        }
    }

    private static void add_city(City city)
    {
        cities.put(Integer.toString(city.geo_id), city);
        if(!states.containsKey(city.GetState().GetName()))
        {
            states.put(city.GetState().GetName(), city.GetState());
        }
    }

    private static class Values
    {
        public int geo_id;
        public String city_name;
        public String county_name;
        public String DMA_name;
        public String state_name;
        public String state_code;
        public String country_name;
        public double latitude;
        public double longitude;
        public int population;

        public Values(String[] values) throws IOException
        {
            geo_id = Integer.parseInt(values[0]);
            city_name = values[1];
            country_name = values[2];
            DMA_name = values[3];
            state_name = values[4];
            state_code = values[5];
            country_name = values[6];
            try
            {
            latitude = Double.parseDouble(values[7]);
            longitude = Double.parseDouble(values[8]);
            population = Integer.parseInt(values[9]);
            }
            catch(Exception e)
            {
                e.getMessage();
            }
        }
    }

    public static class GeoInfo
    {
        private String name;
        private GeoType type;
        private GeoInfo parent;

        public GeoInfo(String name, GeoType type, GeoInfo parent)
        {
            this.name = name;
            this.type = type;
            this.parent = parent;
        }

        public String GetName()
        {
            return name;
        }
        public GeoType GetType()
        {
            return type;
        }

        public GeoInfo GetParent()
        {
            return parent;
        }

        public void SetParent(GeoInfo parent)
        {
            this.parent = parent;
        }
    }

    public static class City extends GeoInfo
    {
         private double latitude;
         private double longitude;
         private int population;
         private int geo_id;

         public City(String name, GeoInfo parent, double latitude, double longitude, int population, int geo_id)
         {
             super(name, GeoType.CITY, parent);
             this.latitude = latitude;
             this.longitude = longitude;
             this.population = population;
             this.geo_id = geo_id;
         }

         private City(Values values)
         {
             super(values.city_name, GeoType.CITY, null);
             if(Geography.Exists(GeoType.STATE, values.state_name))
             {
                SetParent(Geography.GetState(values.state_name));
             }
             else
             {
                 SetParent(new State(values));
             }
             this.latitude = values.latitude;
             this.longitude = values.longitude;
             this.population = values.population;
             this.geo_id = values.geo_id;

             Geography.add_city(this);
         }

         public State GetState()
         {
             return (State)GetParent();
         }

         public double GetLatitude()
         {
             return latitude;
         }

         public double GetLongitude()
         {
             return longitude;
         }

         public int GetPopulation()
         {
             return population;
         }

         public int GetGeoID()
         {
             return geo_id;
         }
    }

    public static class County extends GeoInfo
    {
        public County(String name, GeoInfo parent)
        {
            super(name, GeoType.COUNTY, parent);
        }
    }

    public static class DMA extends GeoInfo
    {
        public DMA(String name, GeoInfo parent)
        {
            super(name, GeoType.DMA, parent);
        }
    }

    public static class State extends GeoInfo
    {
        private String state_code;
        public State(String name, GeoInfo parent, String state_code)
        {
            super(name, GeoType.STATE, Country.USA);
            this.state_code = state_code;
        }

        private State(Values values)
        {
            super(values.state_name, GeoType.STATE, Country.USA);
            this.state_code = values.state_code;
        }
        public String getStateCode() {return state_code;}
    }

    public static class Country extends GeoInfo
    {
        public static Country USA = new Country("USA");

        public Country(String name)
        {
            super(name, GeoType.COUNTRY, null);
        }
    }

}
